package com.wuyangnju.lj.callcenter.model;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import umontreal.iro.lecuyer.randvar.RandomVariateGen;
import umontreal.iro.lecuyer.simevents.Accumulate;
import umontreal.iro.lecuyer.simevents.Event;
import umontreal.iro.lecuyer.simevents.Sim;
import umontreal.iro.lecuyer.stat.Tally;

import com.wuyangnju.lj.callcenter.model.customer.Customer;
import com.wuyangnju.lj.callcenter.model.server.Server;
import com.wuyangnju.lj.callcenter.model.server.policy.AcceptCustomerPolicyForServer0;
import com.wuyangnju.lj.callcenter.model.server.policy.AcceptCustomerPolicyForServer1;
import com.wuyangnju.lj.callcenter.util.HeadCountSnapshot;

public class CallCenter {
	private static final Log log = LogFactory.getLog(CallCenter.class);
	private final ReentrantReadWriteLock lock_;
	private long eventLimit;

	// for pic
	// private HeadCountSnapshot lastSnapshot;
	// private PrintWriter dataFile;

	// for event number limited pic
	private List<HeadCountSnapshot> snapshots;

	// for statistic
	// headCountOne[i] for buffer[i] and server[i]
	private Tally[][] headCountOne;
	private Tally[][] headCount;

	private Accumulate[][] timeAverageOne;
	private Tally[][] timeAverage;

	private RandomVariateGen[] genArr_;
	private RandomVariateGen[] genServ_;
	private RandomVariateGen[] genReg_;

	private Server[] servers = new Server[2];
	private Buffer[] buffers = new Buffer[2];

	private double[] reservePosibility_;

	public CallCenter(int[] limit, double[] reservePosibility,
			RandomVariateGen[] arr, RandomVariateGen[] serv,
			RandomVariateGen[] reg, String simId, long eventLimit) {
		lock_ = new ReentrantReadWriteLock(true);

		for (int i = 0; i < 2; i++) {
			buffers[i] = new Buffer();
		}

		servers[0] = new Server(limit[0], new AcceptCustomerPolicyForServer0(
				this));
		servers[1] = new Server(limit[1], new AcceptCustomerPolicyForServer1(
				this));

		this.reservePosibility_ = reservePosibility;

		genArr_ = arr;
		genServ_ = serv;
		genReg_ = reg;

		this.eventLimit = eventLimit;

		Sim.init();

		// String datFileStr = "callcenter" + simId + ".dat";
		// File datFile = new File(datFileStr);
		// if (datFile.exists()) {
		// datFile.delete();
		// }
		// try {
		// datFile.createNewFile();
		// dataFile = new PrintWriter(datFile);
		// } catch (IOException e) {
		// log.error(e, e);
		// }

		// for draw pic
		int[] b = { 0, 0 };
		int[] s = { 0, 0 };
		int[] s0 = { 0, 0 };
		// lastSnapshot = new HeadCountSnapshot(0.0, b, s);
		// dataFile.println(lastSnapshot.t + "," + lastSnapshot.b[0] + ","
		// + lastSnapshot.b[1] + "," + lastSnapshot.s[0] + ","
		// + lastSnapshot.s[1]);
		snapshots = new LinkedList<HeadCountSnapshot>();
		snapshots.add(new HeadCountSnapshot(0, b, s, s0));

		// Data Collection - setting
		headCountOne = new Tally[2][4];
		headCount = new Tally[2][4];
		timeAverageOne = new Accumulate[2][2];
		timeAverage = new Tally[2][2];
		for (int i = 0; i < 2; i++) {
			headCountOne[i][0] = new Tally("Probability of Abandonment - One");
			headCountOne[i][1] = new Tally("Probability of Waiting - One");
			headCountOne[i][2] = new Tally("Waiting of served - One");
			headCountOne[i][3] = new Tally("Waiting of reneged - One");

			headCount[i][0] = new Tally("Probability of Abandonment");
			headCount[i][1] = new Tally("Probability of Waiting");
			headCount[i][2] = new Tally("Waiting of served");
			headCount[i][3] = new Tally("Waiting of reneged");

			timeAverageOne[i][0] = new Accumulate("Queue Size - One");
			timeAverageOne[i][1] = new Accumulate("Server Size - One");

			timeAverage[i][0] = new Tally("Queue Size");
			timeAverage[i][1] = new Tally("Server Size");
		}
		// End of Data Collection - setting

	}

	public List<HeadCountSnapshot> getSnapshots() {
		return snapshots;
	}

	public Buffer[] getBuffers() {
		return buffers;
	}

	public Tally[][] getHeadCount() {
		return headCount;
	}

	public Tally[][] getTimeAverage() {
		return timeAverage;
	}

	public void simulate(double timeHorizonOne, int round) {
		Sim.init();
		new EndOfSim().schedule(timeHorizonOne * (double) round + 0.00001);

		for (int i = 1; i <= round; i++)
			new EndOfOneRun().schedule(timeHorizonOne * (double) i);

		for (int i = 0; i < 2; i++) {
			new Arrival(genArr_[i], genServ_[i], genReg_[i], lock_, i,
					reservePosibility_[i]).schedule(genArr_[i].nextDouble());
		}

		Sim.start();
	}

	private boolean goReserve(double p) {
		return Math.random() < p;
	}

	class EndOfOneRun extends Event {
		public void actions() {
			for (int i = 0; i < 2; i++) {
				for (int j = 0; j < 4; j++) {
					headCount[i][j].add(headCountOne[i][j].average());
				}
				for (int j = 0; j < 2; j++) {
					timeAverage[i][j].add(timeAverageOne[i][j].average());
				}
				for (int j = 0; j < 4; j++) {
					headCountOne[i][j].init();
				}
				for (int j = 0; j < 2; j++) {
					timeAverageOne[i][j].init();
				}
			}
		}
	}

	class EndOfSim extends Event {
		public void actions() {
			Sim.stop();
		}
	}

	class Arrival extends Event {
		RandomVariateGen genArr;
		RandomVariateGen genServ;
		RandomVariateGen genReg;
		ReentrantReadWriteLock lock;
		int id;
		double reservePosibility;

		public Arrival(RandomVariateGen genArr, RandomVariateGen genServ,
				RandomVariateGen genReg, ReentrantReadWriteLock lock, int id,
				double reservePosibility) {
			this.genArr = genArr;
			this.genServ = genServ;
			this.genReg = genReg;
			this.lock = lock;
			this.id = id;
			this.reservePosibility = reservePosibility;
		}

		public Arrival(Arrival arrival) {
			this.genArr = arrival.genArr;
			this.genServ = arrival.genServ;
			this.genReg = arrival.genReg;
			this.lock = arrival.lock;
			this.id = arrival.id;
			this.reservePosibility = arrival.reservePosibility;
		}

		public void actions() {
			double t = Sim.time();
			// Generate Arriving Customer
			Customer customer = new Customer(t, genServ.nextDouble(),
					genReg.nextDouble());
			// Generate next arrival event.
			new Arrival(this).schedule(genArr.nextDouble());

			try {
				lock.writeLock().lock();
				if (servers[id].serve(customer)) {
					customer.getService(t);
					new Departure(customer, this).schedule(customer
							.getServiceTime());
				} else {
					buffers[id].admit(customer);
					customer.joinQueue();
					customer.fromBuffer = id;
					new Renege(customer, this).schedule(customer
							.getRenegeTime());
				}

				// Data Collection
				timeAverageOne[id][0].update(buffers[id].size());
				timeAverageOne[id][1].update(servers[id].size());
				// End of Data Collection

				// for draw pic

				if (snapshots.size() >= eventLimit) {
					return;
				}
				int[] b = new int[2], s = new int[2], s0 = new int[2];
				b[id] = buffers[id].size();
				s[id] = servers[id].size();
				// b[1 - id] = snapshots.get(snapshots.size() - 1).b[1 - id];
				// s[1 - id] = snapshots.get(snapshots.size() - 1).s[1 - id];
				b[1 - id] = buffers[1 - id].size();
				s[1 - id] = servers[1 - id].size();
				s0[0] = servers[0].sizeFromBuffer(0);
				s0[1] = servers[0].sizeFromBuffer(1);
				snapshots.add(new HeadCountSnapshot(t, b, s, s0));

				// lastSnapshot = new HeadCountSnapshot(t, b, s);
				// dataFile.println(lastSnapshot.t + "," + lastSnapshot.b[0] +
				// ","
				// + lastSnapshot.b[1] + "," + lastSnapshot.s[0] + ","
				// + lastSnapshot.s[1]);

			} catch (Exception e) {
				log.error(e, e);
			} finally {
				lock.writeLock().unlock();
			}
		}
	}

	class Departure extends Event {
		private Customer customer;
		private ReentrantReadWriteLock lock;
		private int id;
		private double reservePosibility;

		public Departure(Customer customer, Arrival arrival) {
			this.customer = customer;
			this.lock = arrival.lock;
			this.id = arrival.id;
			this.reservePosibility = arrival.reservePosibility;
		}

		public Departure(Customer customer, Departure departure) {
			this.customer = customer;
			this.lock = departure.lock;
			this.id = departure.id;
			this.reservePosibility = departure.reservePosibility;
		}

		public void actions() {

			double t = Sim.time();

			try {
				lock.writeLock().lock();
				servers[id].depart(customer);
				customer.depart();

				if (goReserve(reservePosibility)) {
					new Arrival(genArr_[0], genServ_[0], genReg_[0], lock_, 0,
							reservePosibility_[0]).schedule(genArr_[0]
							.nextDouble());
				}

				Customer nextCustomer = servers[id].acceptCustomer();
				if (nextCustomer != null) {
					servers[id].serve(nextCustomer);
					nextCustomer.getService(t);
					new Departure(nextCustomer, this).schedule(nextCustomer
							.getServiceTime());
				}

				// Data Collection
				headCountOne[id][0].add(0);// cust.reneged);
				headCountOne[id][1].add(customer.isDelayed() ? 1 : 0);
				headCountOne[id][2].add(customer.getWaitTime());

				// should think of another buffer's change
				timeAverageOne[0][0].update(buffers[0].size());
				timeAverageOne[1][0].update(buffers[1].size());
				timeAverageOne[id][1].update(servers[id].size());
				// End of Data Collection

				// for draw pic
				if (snapshots.size() >= eventLimit) {
					return;
				}
				int[] b = new int[2], s = new int[2], s0 = new int[2];
				b[0] = buffers[0].size();
				b[1] = buffers[1].size();
				s[id] = servers[id].size();
				// s[1 - id] = snapshots.get(snapshots.size() - 1).s[1 - id];
				s[1 - id] = servers[1 - id].size();
				s0[0] = servers[0].sizeFromBuffer(0);
				s0[1] = servers[0].sizeFromBuffer(1);
				snapshots.add(new HeadCountSnapshot(t, b, s, s0));
				// lastSnapshot = new HeadCountSnapshot(t, b, s);
				// dataFile.println(lastSnapshot.t + "," + lastSnapshot.b[0] +
				// ","
				// + lastSnapshot.b[1] + "," + lastSnapshot.s[0] + ","
				// + lastSnapshot.s[1]);
				// for draw pic end

			} catch (Exception e) {
				log.error(e, e);
			} finally {
				lock.writeLock().unlock();
			}
		}
	}

	class Renege extends Event {
		private Customer customer;
		private ReentrantReadWriteLock lock;
		private int id;

		public Renege(Customer customer, Arrival arrival) {
			this.customer = customer;
			this.lock = arrival.lock;
			this.id = arrival.id;
		}

		public void actions() {
			try {
				lock.writeLock().lock();
				// if that is really an abandonment
				if (customer.isInBuffer()) {
					double t = Sim.time();
					buffers[id].renege(customer);
					customer.renege(t);

					// Data Collection
					headCountOne[id][0].add(customer.isReneged() ? 1 : 0);
					headCountOne[id][1].add(customer.isDelayed() ? 1 : 0);
					headCountOne[id][3].add(customer.getWaitTime());

					timeAverageOne[id][0].update(buffers[id].size());
					timeAverageOne[id][1].update(servers[id].size());
					// End of Data Collection

					// for draw pic
					if (snapshots.size() >= eventLimit) {
						return;
					}
					int[] b = new int[2], s = new int[2], s0 = new int[2];
					b[id] = buffers[id].size();
					s[id] = servers[id].size();
					// b[1 - id] = snapshots.get(snapshots.size() - 1).b[1 -
					// id];
					// s[1 - id] = snapshots.get(snapshots.size() - 1).s[1 -
					// id];
					b[1 - id] = buffers[1 - id].size();
					s[1 - id] = servers[1 - id].size();
					s0[0] = servers[0].sizeFromBuffer(0);
					s0[1] = servers[0].sizeFromBuffer(1);
					snapshots.add(new HeadCountSnapshot(t, b, s, s0));
					// lastSnapshot = new HeadCountSnapshot(t, b, s);
					// dataFile.println(lastSnapshot.t + "," + lastSnapshot.b[0]
					// + "," + lastSnapshot.b[1] + "," + lastSnapshot.s[0]
					// + "," + lastSnapshot.s[1]);
					// for draw pic end
				}
			} catch (Exception e) {
				log.error(e, e);
			} finally {
				lock.writeLock().unlock();
			}
		}
	}
}
